﻿using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;

namespace ServerUtils
{
    public class OperationInvoker : LogGenerator
    {
        private long operationId;
        private readonly string operationInvokerId;

        public OperationInvoker(string operationInvokerId)
        {
            this.operationInvokerId = operationInvokerId;
        }

        public bool LogOperation { get; set; }

        public string OperationInvokerId
        {
            get { return this.operationInvokerId; }
        }

        public TResult Invoke<TResult>(IOperation<TResult> operation) where TResult : OperationResult, new()
        {
            TResult result;
            operation.OperationId = Interlocked.Increment(ref this.operationId);

            try
            {
                result = operation.Execute();
                this.PostExecute(operation, result);
            }
            catch (Exception ex)
            {
                result = this.HandleException<TResult>(ex, operation);
            }

            return result;
        }

        public TResult Invoke<TResult>(Func<TResult> func, object request, [CallerMemberName]string operationName = "") where TResult : OperationResult, new()
        {
            FuncOperation<TResult> operation = new FuncOperation<TResult>(operationName, request, func);
            return this.Invoke(operation);
        }

        public async Task<TResult> InvokeAsync<TResult>(IAsyncOperation<TResult> operation) where TResult : OperationResult, new()
        {
            TResult result;
            operation.OperationId = Interlocked.Increment(ref this.operationId);

            try
            {
                result = await operation.ExecuteAsync();
                this.PostExecute(operation, result);
            }
            catch (Exception ex)
            {
                result = this.HandleException<TResult>(ex, operation);
            }

            return result;
        }

        public Task<TResult> InvokeAsync<TResult>(Func<Task<TResult>> func, object request, [CallerMemberName]string operationName = "") where TResult : OperationResult, new()
        {
            FuncAsyncOperation<TResult> operation = new FuncAsyncOperation<TResult>(operationName, request, func);
            return this.InvokeAsync(operation);
        }

        private void PostExecute<TResult>(IOperationDetails operation, TResult result) where TResult : OperationResult
        {
            string textRequest = null;
            string textResult = null;

            if (this.LogOperation)
            {
                textRequest = operation.Request.Serialize();
                textResult = result.Serialize();
                this.LogRequest(operation.OperationId, textRequest, textResult, operation.Name);
            }

            if (result.ResultType != OperationResultType.Succeed)
            {
                if (textRequest == null)
                {
                    textRequest = operation.Request.Serialize();
                }

                if (textResult == null)
                {
                    textResult = result.Serialize();
                }

                this.LogUnsuccessfulOperation(operation.OperationId, textRequest, textResult, operation.Name);
            }
        }

        private TResult HandleException<TResult>(Exception ex, IOperationDetails operation) where TResult : OperationResult, new()
        {
            var textRequest = operation.Request.Serialize();
            this.LogError(operation.OperationId, textRequest, operation.Name, ex.ToString());
            return new TResult { ResultType = OperationResultType.Error, Message = "Error" };
        }

        public void LogError(long id, string textRequest, string operationName, string error)
        {
            var logEventArgs = new LogEventArgs(
                EventLogEntryType.Error,
                "Error in operation:\r\nOperationInvokerId: {0}, OperationId: {1}, OperationName: {2}\r\nRequest:\r\n{3}\r\nError:\r\n{4}",
                this.OperationInvokerId,
                id,
                operationName,
                textRequest,
                error);

            this.OnLog(logEventArgs);
        }

        private void LogUnsuccessfulOperation(long id, string textRequest, string textResult, string operationName)
        {
            var logEventArgs = new LogEventArgs(
                EventLogEntryType.Warning,
                "Unsuccessful operation:\r\nOperationInvokerId: {0}, OperationId: {1}, OperationName: {2}\r\nRequest:\r\n{3}\r\nResult:\r\n{4}",
                this.OperationInvokerId,
                id,
                operationName,
                textRequest,
                textResult);

            this.OnLog(logEventArgs);
        }

        private void LogRequest(long id, string textRequest, string textResult, string operationName)
        {
            var logEventArgs = new LogEventArgs(
                EventLogEntryType.Information,
                "Request:\r\nOperationInvokerId: {0}, OperationId: {1}, OperationName: {2}\r\nRequest Parameter:\r\n{3}\r\nResult:\r\n{4}",
                this.OperationInvokerId,
                id,
                operationName,
                textRequest == string.Empty ? "None" : textRequest,
                textResult);

            this.OnLog(logEventArgs);
        }
    }
}
